﻿using System;
using System.Linq;
using Urs.Core.Caching;
using Urs.Data.Domain.Tasks;
using Urs.Core.Infrastructure;
using Urs.Services.Logging;

namespace Urs.Services.Tasks
{
    public partial class Task
    {
        private bool? _enabled;
        private Task()
        {
            this.Enabled = true;
        }

        public Task(ScheduleTask task)
        {
            ScheduleTask = task;
        }


        #region Utilities

        private void ExecuteTask()
        {
            var scheduleTaskService = EngineContext.Current.Resolve<IScheduleTaskService>();

            if (!Enabled)
                return;

            var type = Type.GetType(ScheduleTask.Type) ??
                AppDomain.CurrentDomain.GetAssemblies()
                .Select(a => a.GetType(ScheduleTask.Type))
                .FirstOrDefault(t => t != null);
            if (type == null)
                throw new Exception($"Schedule task ({ScheduleTask.Type}) cannot by instantiated");

            object instance = null;
            try
            {
                instance = EngineContext.Current.Resolve(type);
            }
            catch
            {
            }

            if (instance == null)
            {
                instance = EngineContext.Current.ResolveUnregistered(type);
            }

            var task = instance as IScheduleTask;
            if (task == null)
                return;

            ScheduleTask.LastStartTime = DateTime.UtcNow;
            scheduleTaskService.UpdateTask(ScheduleTask);
            task.Execute();
            ScheduleTask.LastEndTime = ScheduleTask.LastSuccessTime = DateTime.UtcNow;
            scheduleTaskService.UpdateTask(ScheduleTask);
        }

        protected virtual bool IsTaskAlreadyRunning(ScheduleTask scheduleTask)
        {
            if (!scheduleTask.LastStartTime.HasValue && !scheduleTask.LastEndTime.HasValue)
                return false;

            var lastStartUtc = scheduleTask.LastStartTime ?? DateTime.UtcNow;

            if (scheduleTask.LastEndTime.HasValue && lastStartUtc < scheduleTask.LastEndTime)
                return false;

            if (lastStartUtc.AddSeconds(scheduleTask.Seconds) <= DateTime.UtcNow)
                return false;

            return true;
        }

        #endregion

        public void Execute(bool throwException = false, bool ensureRunOncePerPeriod = true)
        {
            if (ScheduleTask == null || !Enabled)
                return;

            if (ensureRunOncePerPeriod)
            {
                if (IsTaskAlreadyRunning(ScheduleTask))
                    return;

                if (ScheduleTask.LastStartTime.HasValue && (DateTime.UtcNow - ScheduleTask.LastStartTime).Value.TotalSeconds < ScheduleTask.Seconds)
                    return;
            }

            try
            {
                var expirationInSeconds = Math.Min(ScheduleTask.Seconds, 300) - 1;
                var expiration = TimeSpan.FromSeconds(expirationInSeconds);
                //todo
                //var locker = EngineContext.Current.Resolve<ILocker>();
                //locker.PerformActionWithLock(ScheduleTask.Type, expiration, ExecuteTask);
            }
            catch (Exception exc)
            {
                var scheduleTaskService = EngineContext.Current.Resolve<IScheduleTaskService>();

                ScheduleTask.Enabled = !ScheduleTask.StopOnError;
                ScheduleTask.LastEndTime = DateTime.UtcNow;
                scheduleTaskService.UpdateTask(ScheduleTask);

                var logger = EngineContext.Current.Resolve<ILogger>();
                logger.Error($"Error while running the '{ScheduleTask.Name}' schedule task. {exc.Message}", exc);
                if (throwException)
                    throw;
            }
        }

        #region Properties

        public ScheduleTask ScheduleTask { get; }

        public bool Enabled
        {
            get
            {
                if (!_enabled.HasValue)
                    _enabled = ScheduleTask?.Enabled;

                return _enabled.HasValue && _enabled.Value;
            }

            set => _enabled = value;
        }

        #endregion
    }
}
